/**
 * MojoSetup; a portable, flexible installation application.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Steffen Pankratz.
 *
      Copyright (c) 2006-2010 Ryan C. Gordon and others.

   This software is provided 'as-is', without any express or implied warranty.
   In no event will the authors be held liable for any damages arising from
   the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software in a
   product, an acknowledgment in the product documentation would be
   appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.

   3. This notice may not be removed or altered from any source distribution.

       Ryan C. Gordon <icculus@icculus.org>
 *
 */

#include "fileio.h"
#include "platform.h"

#if !SUPPORT_PKG
MojoArchive *MojoArchive_createPKG(MojoInput *io) { return NULL; }
#else

#define PKG_MAGIC 0x4f504b47

typedef struct
{
    uint32 magic;       // 4 bytes, has to be PKG_MAGIC (0x4f504b47)
    uint32 fileCount;   // 4 bytes, number of files in the archive
} PKGheader;

typedef struct
{
    uint64 nextFileStart;
} PKGinfo;

static boolean MojoInput_pkg_ready(MojoInput *io)
{
    return true;
} // MojoInput_pkg_ready

static int64 MojoInput_pkg_read(MojoInput *io, void *buf, uint32 bufsize)
{
    MojoArchive *ar = (MojoArchive *) io->opaque;
    const MojoArchiveEntry *entry = &ar->prevEnum;
    int64 pos = io->tell(io);
    if ((pos + bufsize) > entry->filesize)
        bufsize = (uint32) (entry->filesize - pos);
    return ar->io->read(ar->io, buf, bufsize);
} // MojoInput_pkg_read

static boolean MojoInput_pkg_seek(MojoInput *io, uint64 pos)
{
    MojoArchive *ar = (MojoArchive *) io->opaque;
    const PKGinfo *info = (PKGinfo *) ar->opaque;
    const MojoArchiveEntry *entry = &ar->prevEnum;
    boolean retval = false;
    if (pos < ((uint64) entry->filesize))
    {
        const uint64 newpos = (info->nextFileStart - entry->filesize) + pos;
        retval = ar->io->seek(ar->io, newpos);
    } // if
    return retval;
} // MojoInput_pkg_seek

static int64 MojoInput_pkg_tell(MojoInput *io)
{
    MojoArchive *ar = (MojoArchive *) io->opaque;
    const PKGinfo *info = (PKGinfo *) ar->opaque;
    const MojoArchiveEntry *entry = &ar->prevEnum;
    return ar->io->tell(ar->io) - (info->nextFileStart - entry->filesize);
} // MojoInput_pkg_tell

static int64 MojoInput_pkg_length(MojoInput *io)
{
    MojoArchive *ar = (MojoArchive *) io->opaque;
    const MojoArchiveEntry *entry = &ar->prevEnum;
    return entry->filesize;
} // MojoInput_pkg_length

static MojoInput *MojoInput_pkg_duplicate(MojoInput *io)
{
    MojoInput *retval = NULL;
    fatal(_("BUG: Can't duplicate pkg inputs"));  // !!! FIXME: why not?
    return retval;
} // MojoInput_pkg_duplicate

static void MojoInput_pkg_close(MojoInput *io)
{
    free(io);
} // MojoInput_pkg_close

// MojoArchive implementation...

static boolean MojoArchive_pkg_enumerate(MojoArchive *ar)
{
    PKGinfo *info = (PKGinfo *) ar->opaque;
    MojoArchive_resetEntry(&ar->prevEnum);

    info->nextFileStart = sizeof (PKGheader);
    return true;
} // MojoArchive_pkg_enumerate


static const MojoArchiveEntry *MojoArchive_pkg_enumNext(MojoArchive *ar)
{
    PKGinfo *info = (PKGinfo *) ar->opaque;
    MojoInput *io = ar->io;
    int64 ret = 0;
    uint32 pathNameLength = 0;
    uint64 pathNameStart = 0;
    uint32 fileNameLength = 0;
    uint64 fileNameStart = 0;
    uint32 fileSize = 0;
    char* backSlash = NULL;

    if (!ar->io->seek(ar->io, info->nextFileStart))
        return NULL;

    // read the path name length
    if (!MojoInput_readui32(io, &pathNameLength))
            return NULL;

    pathNameStart = ar->io->tell(ar->io);

    // skip reading the path name for now
    if (!ar->io->seek(ar->io, pathNameStart + pathNameLength))
        return NULL;

    // read the file name length
    if (!MojoInput_readui32(io, &fileNameLength))
            return NULL;

    fileNameStart = ar->io->tell(ar->io);

    // as both strings are null terminated, we need one byte less
    ar->prevEnum.filename = (char *) xmalloc(pathNameLength + fileNameLength -1);

    // go to the start of the path name
    if (!ar->io->seek(ar->io, pathNameStart))
        return NULL;

    // read the path name
    ret = io->read(io, ar->prevEnum.filename, pathNameLength);
    if (ret != pathNameLength)
        return false;

    // replace backslashes with slashes in the path name
    while((backSlash = strchr(ar->prevEnum.filename, '\\')))
        *backSlash = '/';

    // go the start of the file name
    if (!ar->io->seek(ar->io, fileNameStart))
        return NULL;

    // read the file name
    ret = io->read(io, ar->prevEnum.filename + pathNameLength - 1, fileNameLength);
    if (ret != fileNameLength)
        return false;

    // read the file size
    if (!MojoInput_readui32(io, &fileSize))
        return NULL;

    // skip the next 8 bytes, probably some kind of check sum
    if (!ar->io->seek(ar->io, ar->io->tell(ar->io) + 8))
        return NULL;

    ar->prevEnum.filesize = fileSize;
    ar->prevEnum.perms = MojoPlatform_defaultFilePerms();
    ar->prevEnum.type = MOJOARCHIVE_ENTRY_FILE;

    info->nextFileStart = ar->io->tell(ar->io) + ar->prevEnum.filesize;

    return &ar->prevEnum;
} // MojoArchive_pkg_enumNext


static MojoInput *MojoArchive_pkg_openCurrentEntry(MojoArchive *ar)
{
    MojoInput *io = NULL;
    io = (MojoInput *) xmalloc(sizeof (MojoInput));
    io->ready = MojoInput_pkg_ready;
    io->read = MojoInput_pkg_read;
    io->seek = MojoInput_pkg_seek;
    io->tell = MojoInput_pkg_tell;
    io->length = MojoInput_pkg_length;
    io->duplicate = MojoInput_pkg_duplicate;
    io->close = MojoInput_pkg_close;
    io->opaque = ar;
    return io;
} // MojoArchive_pkg_openCurrentEntry


static void MojoArchive_pkg_close(MojoArchive *ar)
{
    PKGinfo *info = (PKGinfo *) ar->opaque;
    ar->io->close(ar->io);

    free(info);
    free(ar);
} // MojoArchive_pkg_close


MojoArchive *MojoArchive_createPKG(MojoInput *io)
{
    MojoArchive *ar = NULL;
    PKGinfo *pkgInfo = NULL;
    PKGheader pkgHeader;

    if (!MojoInput_readui32(io, &pkgHeader.magic))
        return NULL;
    else if (!MojoInput_readui32(io, &pkgHeader.fileCount))
        return NULL;

    // Check if this is a *.pkg file.
    if (pkgHeader.magic != PKG_MAGIC)
        return NULL;

    pkgInfo = (PKGinfo *) xmalloc(sizeof (PKGinfo));

    ar = (MojoArchive *) xmalloc(sizeof (MojoArchive));
    ar->opaque = pkgInfo;
    ar->enumerate = MojoArchive_pkg_enumerate;
    ar->enumNext = MojoArchive_pkg_enumNext;
    ar->openCurrentEntry = MojoArchive_pkg_openCurrentEntry;
    ar->close = MojoArchive_pkg_close;
    ar->io = io;

    return ar;
} // MojoArchive_createPKG

#endif // SUPPORT_PKG

// end of archive_pkg.c ...
